<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Web3\Contracts\Types\Address;
use Web3\Contracts\Types\Boolean;
use Web3\Contracts\Types\Bytes;
use Web3\Contracts\Types\DynamicBytes;
use Web3\Contracts\Types\Integer;
use Web3\Contracts\Types\Str;
use Web3\Contracts\Types\Uinteger;
use Web3\Utils;

class EthNode{

    protected $rpcData;
    protected $url;
    protected $abi;
    protected $centerAddress;
    protected $centerPassword;
    protected $collectionAddress;
    public $commission;
    public function __construct(){
        $this->rpcData = ["id"=>0,"jsonrpc"=>"2.0"];
        $this->url = config('user.erc20_url','16.162.109.55:9508');
        $this->abi = new \Web3\Contracts\Ethabi([
            'address' => new Address,
            'bool' => new Boolean,
            'bytes' => new Bytes,
            'dynamicBytes' => new DynamicBytes,
            'int' => new Integer,
            'string' => new Str,
            'uint' => new Uinteger,
        ]);
        $this->centerAddress = '0x566490dca18c8175a16fe440b8b722c071b47eb1';
        $this->centerPassword = 'xujunjie';
        $this->collectionAddress = '0x3700dfadcdfaa7eb1ed866876361793d46f230f9';
        $this->commission = 0.0002;
    }

    public function toEther($number, $unit){
        return Utils::toEther($number, $unit);
    }

    public function functionSignature($functionName){
        return $this->abi->encodeFunctionSignature($functionName);
    }

    public function encodeParameters($type,$data){
        return substr($this->abi->encodeParameters($type,$data),2);
    }

    public function decodeParameters($type,$data){
        return $this->abi->decodeParameters($type,$data);
    }

    /**
     * 创建地址
     */
    public function createAddress($password){
        $param[] = $password;
        $this->rpcData['method'] = "personal_newAccount";
        $this->rpcData['params'] = $param;
        $result = http_chain($this->url,$this->rpcData);

        if(!empty($result['result'])){
            return $result['result'];
        }else{
            Log::info("erc20创建地址失败");
            return false;
        }
    }

    /**
     * 验证地址
     */
    public function validateAddress($address){
        $utils = new \Web3\Utils();
        return $utils->isAddress($address);
    }

    /**
     * 获取gas价格
     */
    public function getGasPrice(){
        if (Cache::has('gas_price')) {
            return Cache::get('gas_price');
        }
        $this->rpcData['params'] = [];
        $this->rpcData['method'] = "eth_gasPrice";
        $result = http_chain($this->url,$this->rpcData);
        if(!empty($result['result'])){
            Cache::put('gas_price', $result['result'], 14400);
            return $result['result'];
        }
        return false;
    }

    /**
     * 解锁账户
     */
    public function unlockAccount($address,$password){
        $param[] = $address;
        $param[] = $password;
        $param[] = 100;
        $this->rpcData['params'] = $param;
        $this->rpcData['method'] = "personal_unlockAccount";
        $result = http_chain($this->url,$this->rpcData);
        if(!empty($result['result'])){
            return $result['result'];
        }
        return false;
    }

    /**
     * 提现转账
     */
    public function withdraw($toAddress,$number,$contractAddress = NULL){
        $password = $this->centerPassword;
        $fromAddress = $this->centerAddress;
        if(!$this->unlockAccount($fromAddress,$password)){
            return false;
        }
        $gasPrice = $this->getGasPrice();
        if(!empty($contractAddress)){
            $dataStr = '';
            $dataStr .= $this->abi->encodeFunctionSignature('transfer(address,uint256)');
            $dataStr .= substr($this->abi->encodeParameters(['address','uint256'],[$toAddress,$number]),2);
            $tx = ["from"=>$fromAddress,"to"=>$contractAddress,"gasPrice"=>$gasPrice,"gas"=>"0x5208","data"=>$dataStr];
        }else{

            $number = '0x' . gmp_strval($number,16);
            $tx = ["from"=>$fromAddress,"gasPrice"=>$gasPrice,"gas"=>"0x5208","to"=>$toAddress,"value"=>$number];
        }
        return $this->sendTransaction($tx);
    }

    public function collection($fromAddress,$password,$number,$contractAddress = NULL){
        $toAddress = $this->collectionAddress;
        if(!$this->unlockAccount($fromAddress,$password)){
            return false;
        }
        $gasPrice = $this->getGasPrice();
        if(!empty($contractAddress)){
            $dataStr = '';
            $dataStr .= $this->abi->encodeFunctionSignature('transfer(address,uint256)');
            $dataStr .= substr($this->abi->encodeParameters(['address','uint256'],[$toAddress,$number]),2);
            $tx = ["from"=>$fromAddress,"gasPrice"=>$gasPrice,"gas"=>"0x5208","to"=>$contractAddress,"data"=>$dataStr];
        }else{
            $number = '0x' . gmp_strval($number,16);
            $tx = ["from"=>$fromAddress,"gasPrice"=>$gasPrice,"gas"=>"0x5208","to"=>$toAddress,"value"=>$number];
        }
        return $this->sendTransaction($tx);
    }

    public function sendTransaction($tx){
        $param[] = $tx;
        $this->rpcData['method'] = "eth_sendTransaction";
        $this->rpcData['params'] = $param;
        $result = http_chain($this->url,$this->rpcData);
        if(!empty($result['result'])){
            return $result['result'];
        }else{
            Log::info("erc20转账失败:" . json_encode($param));
            return false;
        }
    }

    public function getTradeInfo($tradeId){
        $param = [$tradeId];
        $this->rpcData['method'] = "eth_getTransactionByHash";
        $this->rpcData['params'] = $param;
        $result = http_chain($this->url,$this->rpcData);
        if(empty($result['result'])){
            return false;
        }
        $info = $result['result'];
        $data = [];
        if(empty($info['blockNumber']) && empty($info['blockHash'])) {//未打包
            return false;
        }

        if($info['input'] == '0x'){
            //合约地址
            $data['contract_address'] = '';
            //货币数量
            $data['number'] = gmp_strval($info['value']);
            //接收地址
            $data['receive_address'] = $info['to'];
            $data['confirmations'] = $this->getblockcount()-hexdec($info['blockNumber']);
            $data['trade_id'] = $tradeId;
            $data['send_address'] = $info['from'];
        }else{
//            $data = $this->getReceiptTradeInfo($tradeId);
            $functionDecode = substr($info['input'],0,10);
            if($functionDecode== '0xa9059cbb'){//transfer
                $input = $this->decodeParameters(['address','uint256'],substr($info['input'],10));
                //货币数量
                $data['number'] = gmp_strval((string)$input[1]);
                //接收地址
                $data['receive_address'] = $input[0];
                //合约地址
                $data['contract_address'] = $info['to'];
            }else if($functionDecode == '0x23b872dd'){//transferFrom
                $input = $this->decodeParameters(['address','address','uint256'],substr($info['input'],10));
                //货币数量
                $data['number'] = gmp_strval((string)$input[2]);
                //接收地址
                $data['receive_address'] = $input[1];
                //合约地址
                $data['contract_address'] = $info['to'];
            }
            $data['send_address'] = $info['from'];
            $data['trade_id'] = $tradeId;
            $data['confirmations'] = $this->getblockcount()-hexdec($info['blockNumber']);
        }
        return $data;
    }

    public function getReceiptTradeInfo($tradeId){
        $param = [$tradeId];
        $this->rpcData['method'] = "eth_getTransactionReceipt";
        $this->rpcData['params'] = $param;
        $result = http_chain($this->url,$this->rpcData);
        if(empty($result['result'])){
            return false;
        }
        $info = $result['result'];
        if(empty($info['logs'][0]['topics'][2]) || empty($info['logs'][0]['data'])){
            return false;
        }
        $data = [];
        //合约地址
        $data['contract_address'] = $info['to'];
        //货币数量
        $data['number'] = gmp_strval($info['logs'][0]['data']);
        //接收地址
        $data['receive_address'] = $this->abi->decodeParameter('address',$info['logs'][0]['topics'][2]);
        $data['confirmations'] = $this->getblockcount()-hexdec($info['blockNumber']);
        $data['trade_id'] = $tradeId;
        $data['send_address'] = $info['from'];
        return $data;
    }

    /**
     * 获取最新块
     */
    public function getblockcount(){
        $param = [];
        $this->rpcData['method'] = "eth_blockNumber";
        $this->rpcData['params'] = $param;
        $result = http_chain($this->url,$this->rpcData);
        if(!empty($result['result'])){
            return hexdec($result['result']);
        }else{
            return false;
        }
    }

    /**
     * 获取块详情
     */
    public function getBlockByNumber($number){
        $number = '0x' . dechex($number);
        $param = [$number,true];
        $this->rpcData['params'] = $param;
        $this->rpcData['method'] = "eth_getBlockByNumber";
        $result = http_chain($this->url,$this->rpcData);
        if(!empty($result['result'])){
            return $result['result'];
        }else{
            return false;
        }
    }

    /**
     * 获取余额
     */
    public function getBalance($address,$contractAddress = NULL){
        if(empty($contractAddress)){
            $param = [$address,"latest"];
            $this->rpcData['method'] = "eth_getBalance";
            $this->rpcData['params'] = $param;
            $result = http_chain($this->url,$this->rpcData);
            if(!empty($result['result'])){
                return gmp_strval((string)$result['result']);
            }else{
                return false;
            }
        }else{
            $dataStr = '';
            $dataStr .= $this->abi->encodeFunctionSignature('balanceOf(address)');
            $dataStr .= substr($this->abi->encodeParameter('address',$address),2);
            $tx = ["from"=>$address,"to"=>$contractAddress,"data"=>$dataStr];
            $param = [$tx,"latest"];
            $this->rpcData['method'] = "eth_call";
            $this->rpcData['params'] = $param;
            $result = http_chain($this->url,$this->rpcData);
            if(!empty($result['result'])){
                return gmp_strval((string)$result['result']);
            }else{
                return false;
            }
        }
    }

}
